package com.redstar.interesting.common.starter.multiDataSource;

import com.redstar.interesting.common.starter.util.SpringUtils;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.Objects;


/**
 * @author lihongxing
 */
@Aspect
@Component
public class DynamicDataSourceAspect {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    private static final String DS = "DataSource";


    @Pointcut("@annotation(com.redstar.interesting.common.starter.multiDataSource.DS)" +
            "|| @within(com.redstar.interesting.common.starter.multiDataSource.DS)")
    public void pointCut(){}


    @Around("pointCut()")
    public Object around(ProceedingJoinPoint jpt) throws Throwable{
        DS dataSource = getDataSource(jpt);
        if (Objects.nonNull(dataSource)) {
            log.info("切换数据源:{}", dataSource.value().name());
            DynamicDataSourceContext.setDataSource(dataSource.value().name());
            //切换
            switchDataSource(dataSource);
        }
        try {
            return jpt.proceed();
        }finally {
            DynamicDataSourceContext.clearDataSource();
        }
    }

    /**
     * 由于 {@link DynamicDataSource#determineCurrentLookupKey()} 方法未按预期执行，所以此处手动切换数据库
     * @param annotation
     * @throws Exception
     */
    private void switchDataSource(DS annotation) throws Exception {
        //首先取出我们需要设置的目标数据源 小写
        DataSource dataSource = SpringUtils.getBean(annotation.value().name().toLowerCase(Locale.ROOT) + DS, DataSource.class);
        //当前准备执行的数据源
        SqlSessionFactory sqlSessionFactory = SpringUtils.getBean(SqlSessionFactory.class);
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Environment environment  = configuration.getEnvironment();
        Field dataSourceField = environment.getClass().getDeclaredField("dataSource");
        dataSourceField.setAccessible(true);
        dataSourceField.set(environment, dataSource);
    }


    private DS getDataSource(ProceedingJoinPoint jpt){
        MethodSignature methodSignature = (MethodSignature) jpt.getSignature();
        //先从方法签名上获取注解
        DS annotation = AnnotationUtils.findAnnotation(methodSignature.getMethod(), DS.class);
        if (Objects.nonNull(annotation)) {
            return annotation;
        }
        //再从类上获取注解
        return AnnotationUtils.findAnnotation(methodSignature.getDeclaringType(), DS.class);
    }

}
